Canigó - Servei d'Excepcions 2.3.x
SERVEI D'EXCEPCIONS
IntroduccióPropòsitLa gestió d'excepcions permet informar que s'ha produït un error al realitzar una petició. Aquest error podrà ser tractat adequadament i en cas necessari informar a l'usuari, llençar una traça, enviar un correu, etc. La gestió correcta de les excepcions és molt important i crítica, però generalment la forma en la que es generen i gestionen les excepcions en les aplicacions és un dels aspectes més ignorats en el disseny de les mateixes. L'ús apropiat de les excepcions fa que els nostres aplicatius siguin més robusts, més fàcils de desenvolupar i mantenir, més lliures d'errors i més fàcils d'utilitzar. Per aquest motiu és important que donem el màxim de detall en les excepcions. Per evitar l'ús innecessari de blocs 'try-catch' dins el codi dels nostres aplicatius Canigó proporciona un mecanisme d'intercepció, pel qual indicarem quines excepcions volem tractar i quins gestors les tractaran sense haver d'incorporar cap referència a cap classe de l'aplicació. Context i Escenaris d'ÚsEl Servei de Traces es troba dins dels serveis de Propòsit General de Canigó. El seu ús és imprescindible en Canigó, ja que tots els serveis utilitzen la gestió d'excepcions definida per aquest servei.
Versions i DependènciesLes dependències descrites a la següent url són requerides per tal de compilar i fer funcionar el projecte: A qui va dirigitAquest document va dirigit als següents perfils:
Documents i Fonts de ReferènciaGlossariAOP (Aspect Oriented Programming) AOP permet definir funcionalitats comunes a diferents components d'un aplicatiu de forma única i amb un tractament general i localitzat, mitjançant la intercepció de la funcionalitat pròpia del mateix. Aquestes funcionalitats comunes es denominen aspectes. Aspectwerkz AspectWerkz és un framework AOP que ofereix gran simplicitat permetent la definició d'aspectes, advices i introduccions amb classes POJOs. A més permet que els aspectes puguin ser definits mitjançant anotacions Java 5, doclets per JDK 1.3 i 1.4 o fins i tot mitjançant un fitxer XML simple. El codi d'intercepció s'afegeix de forma automàtica al bytecode de la classe original mitjançant una compilació adicional. Aquest 'bytecode' modificat canvia la forma de cridar els nostres mètodes sense canviar la funcionalitat, i afegir així els conceptes importants en la programació orientada a aspectes: joinpoint, advice, etc... Checked Exception Tipus d'excepció que hereta de java.lang.Exception. El llenguatge Java obliga a què aquestes excepcions hagin de ser capturades mitjançant un bloc 'try-catch' o bé declarar al mètode que es torna a llençar l'excepció (finalment algú l'haurà de capturar) mitjançant la clàusula 'throws'. Unchecked Exception A diferència de les checked exceptions, poden ser ignorades i per tant no és necessari capturar-les ni declarar-les, encara que com veurem posteriorment, és una bona pràctica declarar-les en la signatura del mètode. Descripció DetalladaArquitectura i ComponentsCanigó ofereix una arquitectura d'excepcions totalment deslligada de qualsevol implementació. Únicament es basa en les característiques de les excepcions del llenguatge Java. Els components podem classificar-los en:
Es pot trobar tota la documentació JavaDoc i el codi font referent aquests components a les següents urls: JavaDoc: http://canigo.ctti.gencat.net/confluence/canigodocs/site/canigo2_0/canigo-services-exceptions/apidocs/index.html Instal.lació i ConfiguracióInstal.lacióLa instal.lació del servei requereix de la utilització de la llibreria 'canigo-services-exceptions' i les dependències indicades a l'apartat 'Introducció-Versions i Dependències'. ConfiguracióLa configuració del Servei d'Excepcions implica 3 pasos:
1. Definició dels gestors d'Excepcions Path desenvolupament: main/resources/spring/canigo-services-exceptions.xml Per cada gestor d'excepcions que volem crear (es pot crear un únic per una classe pare d'excepció, una per cada subtipus d'excepció, ..) crearem un bean de configuració amb la següent informació: Atributs:
Podem afegir propietats a cadascun dels gestors (per exemple el seu Servei de Traces) dins la definició del gestor. Exemple: <bean id="systemExceptionHandler" class="net.gencat.ctti.canigo.services.exceptions.handlers.SystemExceptionHandler"> <property name="loggingService"> <ref local="loggingService"/> </property> </bean> 2. Definició del mapeig de tipus d'excepcions i gestors Path desenvolupament: main/resources/spring/canigo-services-exceptions.xml Atributs:
Propietats:
Exemple:
<bean id="net.gencat.ctti.canigo.services.exceptions.aop.aspectwerkz.ExceptionHandlerAspect" class="net.gencat.ctti.canigo.services.exceptions.aop.aspectwerkz.ExceptionHandlerAspect" singleton="false"> <property name="exceptionHandlers"> <map> <entry> <key><value>net.gencat.ctti.canigo.services.exceptions.SystemException</value></key> <ref bean="systemExceptionHandler"/> <key><value>net.gencat.ctti.canigo.services.mail.exception.MailServiceException</value></key> <ref bean="unAltreExceptionHandler"/> ... <!-- tantes entrades com gestors --> </entry> </map> </property> </bean> A l'exemple es mostra com s'ha configurat el gestor 'systemExceptionHandler' per l'excepció de tipus 'net.gencat.ctti.canigo.services.exceptions.SystemException'. 2. Definició del mecanisme d'intercepció automàtica Path desenvolupament: main/resources/aop.xml Aquest fitxer defineix: les característiques de l'AOP AspectWerkz per definir l'aspecte. Per a més informació es recomana consultar 'http://aspectwerkz.codehaus.org/'. Definirem els següents elements:
Definir els següents atributs:
ExceptionHandlerAspect: classe que defineix la funcionalitat comuna implementada per aquest aspecte. L'atribut container indica la classe que proporciona les instàncies del gestor <aspect class="ExceptionHandlerAspect" container="net.gencat.ctti.canigo.services.exceptions.aop.aspectwerkz.container.SpringAspectContainer"> </aspect>
Definir els següents atributs:
<aspectwerkz> <system id="ExceptionHandlingAspect"> <package name="net.gencat.ctti.canigo.services.exceptions.aop.aspectwerkz"> <aspect class="ExceptionHandlerAspect" container="net.gencat.ctti.canigo.services.exceptions.aop.aspectwerkz.container.SpringAspectContainer"> <advice type="after throwing(exception)" bind-to="execution(* *.*(..))" name="handleException(java.lang.Throwable exception)"/> </aspect> </package> </system> </aspectwerkz> L'expressió regular definida dins la propietat bind-to hauria de ser qualsevol expressió vàlida d'acord amb 'Join point selection pattern language', que es pot consultar a la següent url: http://aspectwerkz.codehaus.org/definition_issues.html; tanmateix, ens hem d'assegurar que el nom complet package+classe està inclòs en l'expressió regular. Si no es troba cap coincidència amb el patró donat, AspectWerkz no serà capaç d'identificar correctament el mètode. Per exemple, si es volguessin interceptar únicament els mètodes dins d'una classe DAO, el patró seria: execution(* DAO.(..)) Utilització del ServeiCreació d'ExcepcionsLa creació d'excepcions és un aspecte de disseny molt important. Quants tipus d'excepcions generar? La resposta no és única i si bé existeixen diferents possibilitats la recomanació és:
A més, seguirem els següents patrons d'ús:
Declaració d'Excepcions llençades als mètodesSegons si l'excepció és de subtipus 'BusinessException' o de subtipus 'SystemException' es seguiran diferents estratègies:
En cas de que el mètode llenci una excepció de subtipus 'BusinessException' declarar 'throws Exception' (excepció genèrica). Com hem comentat, BusinessException és una excepció de tipus 'checked', el que implica que segons el mecanisme tradicional de Java qualsevol classe que rebi l'excepció hauria de control.lar-la mitjançant 'try-catch' o declarar-la de nou als seus mètodes (ja que si no es fa així es produeix un error de compilació). Segons el comentat, evitem el tractament innecessari declarant l'excepció amb 'throws Exception'. Aquesta incorporació és necessària per a que el compilador no es queixi de que no s'ha declarat l'excepció. Exemple:
public interface AccountBO { public void save(Account account) throws Exception; public void saveOrUpdate(Account account) throws Exception; public void update(Account account) throws Exception; public void delete(Account account) throws Exception ; public Account load(Account account); }
Si el mètode llença una excepció de tipus pare 'SystemException' hem de mantenir l'excepció llançada, ja que aquestes excepcions, per ser de tipus 'unchecked' no requereixen del control al nivell superior. public void send(String from, String subject, String aMessage, boolean isHtml, String to) throws MailServiceException; Generació d'ExcepcionsLa generació d'excepcions es realitza mitjançant la clàusula 'throws' de Java. En la generació de les excepcions afegirem el màxim de detall possible mitjançant l'ús de la classe 'ExceptionDetails' (consultar l'apartat 'Arquitectura i Components' per a més informació). Aquesta classe permet que es pugui informar entre d'altres del nivell (Level), de la capa (Layer) i del subsistema (Subsystem) en què s'ha produit l'excepció que llencen l'excepció. A més, es pot informar un codi d'error (que tindrà la seva descripció en un fitxer de propietats) i unes propietats adicionals que serveixen per depurar l'error. El patró recomanat per llençar una excepció és el mostrat a continuació:
ExceptionDetails exDetails = new ExceptionDetails(...) exDetails.setProperties(...); ... throw new XXXException(exDetails); Encapsulació d'Excepcions rebudes de terceres partsSi volem integrar tercers components externs a Canigó és convenient que s'encapsulin les excepcions rebudes en el mecanisme d'excepcions de Canigó. El patró de tractament pot ser com el mostrat a continuació:
try { ... cridaServeiExtern(); } catch(ServeiExternException ex) { ExceptionDetails exDetails = new ExceptionDetails("codiExcepcio", ...,Layer.XXXX,Subsystem.XXXX); exDetails.setProperties(mailProperties); throw new XXXCanigoException(ex,exDetails);} Com veiem, capturem l'excepció del component de tercers i li afegim la informació necessària. Per últim llencem l'excepció (important rellançar-la) incorporant com a segon paràmetre els detalls que hem indicat i en el primer paràmetre l'excepció arrel del tercer component. Exemple: package net.gencat.ctti.canigo.services.mail.impl; ... public class SpringMailServiceImpl implements MailService { ... public void send(String from, String subject, String aMessage, boolean isHtml, Map recipients, List attachments) throws MailException { ... try { ... message = this.mailSender.createMimeMessage(); ... helper.setFrom(from); helper.setSubject(subject); helper.setText(aMessage,isHtml); ... this.mailSender.send(message); } catch(MessagingException ex){ ExceptionDetails exDetails = new ExceptionDetails("canigo.services.mail.error_preparing_addresses", null,Layer.SERVICES, Subsystem.MAIL_SERVICES); exDetails.setProperties(mailProperties); throw new MailServiceException(ex,exDetails); } catch(Exception ex){ ExceptionDetails exDetails = new ExceptionDetails("canigo.services.mail.error_sending_mail", null,Layer.SERVICES, Subsystem.MAIL_SERVICES); exDetails.setProperties(mailProperties); throw new MailServiceException(ex,exDetails); } } Intercepció i tractament de les ExcepcionsMitjançant l'ús de la programació orientada a aspectes (AOP) i les classes proporcionades pel servei d'excepcions s'obté una solució integral a la gestió intel.ligent d'excepcions. La intercepció de les excepcions es realitza mitjançant el mecanisme definit als fitxers de configuració (veure apartat 'Configuració i Instal.lació). Per definir un gestor s'han de seguir els següents pasos:
Exemple:
package net.gencat.ctti.canigo.services.exceptions.handlers; ... public class SystemExceptionHandler extends ExceptionHandlerAdapter { ... public void handleException(Throwable e) throws Throwable { Log log = this.loggingService.getLog(this.getClass()); log.debug("Exception received: SystemExceptionHandler, Exception catched:" + e); throw e; } ... } Eines de SuportGeneració del bytecode d'intercepció amb AspectWerkzPer tal de què funcioni en execució el mecanisme d'intercepció cal un procés addicional de compilació a les classes. Aquest procés addicional es pot executar en Maven mitjançant el goal 'aspectwerkz:weave' tal i com es mostra en la figura següent:
![]() Aquest procés fa servir les següents variables de configuració que hauran d'estar en el nostre fitxer 'project.properties': <!-- mode de màxima informació en el procés de modificació de bytecode --> maven.aspectwerkz.verbose=true <!-- compilació basada en anotacions --> maven.aspectwerkz.mode=attribdef maven.aspectwerkz.definition.validate=true <!-- fitxer de propietats del servei (veure Configuració) o de definició del nostre aspecte --> maven.aspectwerkz.definition.file.src=$\{basedir\}/src/main/resources/aop.xml <!-- directori a on es copien totes les classes que s'han de passar pel nostre procés addicional de modificació de bytecode --> maven.aspectwerkz.build.dest=$\{basedir\}/target/aspectwerkz/classes <!-- directori a on s'ubicaran les classes modificades. És important que aquest directori sigui el directori WEB-INF/classes de l'aplicació desplegada al servidor --> maven.aspectwerkz.weave.build.dir=$\{basedir\}/.deployables/$\{ ctti.war.wtp.module\}/WEB-INF/classes Per evitar la necessitat manual de realitzar aquest goal, podem incorporar el següent codi 'Maven' en la creació de l'aplicatiu Web o de la llibreria: <u:available file="$\{maven.aspectwerkz.weave.build.dir\}/aop.xml"> <attainGoal name="aspectwerkz:weave"/> <ant:echo>Moving file $\{maven.aspectwerkz.weave.build.dir\}/aop.xml to $\{maven.aspectwerkz.weave.build.dir\}/../aop.xml</ant:echo> <ant:move failonerror="false" file="$\{maven.aspectwerkz.weave.build.dir\}/aop.xml" tofile="$\{maven.aspectwerkz.weave.build.dir\}/../aop.xml"/> </u:available> Integració amb Altres ServeisIntegració amb el Servei de Presentació (Arquitectura Base)Aplicar les estratègies definides prèviament implica que l'excepció és llençada a les capes superiors. Aquestes poden decidir si tractar-la o no tractar-la. Si la petició ha estat realitzada per un usuari en mode on-line és convenient que li informem de l'excepció que s'ha produït. Tota excepció produïda per l'aplicació arriba finalment a la classe principal de l'arquitectura de Canigó 'ExtendedDelegatingTilesRequestProcessor' (veure el document 'Serveis de Presentació' per a més informació). Aquesta classe captura l'excepció rebuda i realitza els següents pasos:
message = new ActionMessage(exDetails.getErrorCode(), Afegeix aquest missatge a l'objecte 'ActionMessages' amb clau 'org.apache.struts.action.ERROR'.
request.setAttribute(Globals.ERROR_KEY, messages); Els errors que s'insereixen fan ús del Servei Multidioma. És necessari afegir al fitxer de multiidioma les següents entrades:
net.gencat.ctti.canigo.services.exceptions.WrappedCheckedException=<br>Error type=\{0\} <br>Localized message=\{1\}<br>Properties=\{2\}<br> Layer=\{3\}<br>Subsystem=\{4\} <br>Error code=\{5\}<br>Arguments=\{6\} net.gencat.ctti.canigo.services.exceptions.SystemException=<br>Error type=\{0\} <br>Localized message=\{1\}<br>Properties=\{2\}<br> Layer=\{3\}<br>Subsystem=\{4\} <br>Error code=\{5\}<br>Arguments=\{6\}
Pàgina per presentar els errors Podem representar els errors en una pàgina JSP fent ús del tag 'messages' de Struts. A continuació es mostra un exemple de la seva utilització, on es mostren els missatges en colors diferents segons la severitat de l'error:
<html:messages id="msg" property="org.apache.struts.action.ERROR"> <div class="error" style="margin-right: 10px; margin-bottom: 3px; margin-top: 3px"> <img src="images/iconWarning.gif" class="icon" alt="Warning" /> <bean:write name="msg" filter="false"/><a href="javascript:showEL('errorDetails')"> <b><bean:message key="jsp.includes.see_error_details"/></b></a> <a href="javascript:showEL('stackTrace')" > <b><bean:message key="jsp.includes.see_stack_trace"/></b></a>]<br> <html:messages id="msg" property="net.gencat.ctti.canigo.services.exceptions.Level.WARNING"> <div id="errorDetails" style="display: none"><br><font color="#ff0000"> <bean:write name="msg" filter="false"/></font> </div> </html:messages> <html:messages id="msg" property="net.gencat.ctti.canigo.services.exceptions.Level.ERROR"> <div id="errorDetails" style="display: none"> <font color="#00cc00"><bean:write name="msg" filter="false"/></font> </div> </html:messages> <html:messages id="msg" property="net.gencat.ctti.canigo.services.exceptions.STACKTRACE"> <div id="stackTrace" style="display: none"><br> <pre><bean:write name="msg" filter="false"/></pre> </div> </html:messages> </div> </html:messages> |